1   /*
2    * Copyright (C) 2008 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.base;
18  
19  import static com.google.common.base.Preconditions.checkNotNull;
20  import static com.google.common.base.Preconditions.checkState;
21  import static java.util.concurrent.TimeUnit.DAYS;
22  import static java.util.concurrent.TimeUnit.HOURS;
23  import static java.util.concurrent.TimeUnit.MICROSECONDS;
24  import static java.util.concurrent.TimeUnit.MILLISECONDS;
25  import static java.util.concurrent.TimeUnit.MINUTES;
26  import static java.util.concurrent.TimeUnit.NANOSECONDS;
27  import static java.util.concurrent.TimeUnit.SECONDS;
28  
29  import com.google.common.annotations.Beta;
30  import com.google.common.annotations.GwtCompatible;
31  
32  import java.util.concurrent.TimeUnit;
33  
34  /**
35   * An object that measures elapsed time in nanoseconds. It is useful to measure
36   * elapsed time using this class instead of direct calls to {@link
37   * System#nanoTime} for a few reasons:
38   *
39   * <ul>
40   * <li>An alternate time source can be substituted, for testing or performance
41   *     reasons.
42   * <li>As documented by {@code nanoTime}, the value returned has no absolute
43   *     meaning, and can only be interpreted as relative to another timestamp
44   *     returned by {@code nanoTime} at a different time. {@code Stopwatch} is a
45   *     more effective abstraction because it exposes only these relative values,
46   *     not the absolute ones.
47   * </ul>
48   *
49   * <p>Basic usage:
50   * <pre>
51   *   Stopwatch stopwatch = Stopwatch.{@link #createStarted createStarted}();
52   *   doSomething();
53   *   stopwatch.{@link #stop stop}(); // optional
54   *
55   *   long millis = stopwatch.elapsed(MILLISECONDS);
56   *
57   *   log.info("time: " + stopwatch); // formatted string like "12.3 ms"</pre>
58   *
59   * <p>Stopwatch methods are not idempotent; it is an error to start or stop a
60   * stopwatch that is already in the desired state.
61   *
62   * <p>When testing code that uses this class, use
63   * {@link #createUnstarted(Ticker)} or {@link #createStarted(Ticker)} to
64   * supply a fake or mock ticker.
65   * <!-- TODO(kevinb): restore the "such as" --> This allows you to
66   * simulate any valid behavior of the stopwatch.
67   *
68   * <p><b>Note:</b> This class is not thread-safe.
69   *
70   * @author Kevin Bourrillion
71   * @since 10.0
72   */
73  @Beta
74  @GwtCompatible(emulated = true)
75  public final class Stopwatch {
76    private final Ticker ticker;
77    private boolean isRunning;
78    private long elapsedNanos;
79    private long startTick;
80  
81    /**
82     * Creates (but does not start) a new stopwatch using {@link System#nanoTime}
83     * as its time source.
84     *
85     * @since 15.0
86     */
87    public static Stopwatch createUnstarted() {
88      return new Stopwatch();
89    }
90  
91    /**
92     * Creates (but does not start) a new stopwatch, using the specified time
93     * source.
94     *
95     * @since 15.0
96     */
97    public static Stopwatch createUnstarted(Ticker ticker) {
98      return new Stopwatch(ticker);
99    }
100 
101   /**
102    * Creates (and starts) a new stopwatch using {@link System#nanoTime}
103    * as its time source.
104    *
105    * @since 15.0
106    */
107   public static Stopwatch createStarted() {
108     return new Stopwatch().start();
109   }
110 
111   /**
112    * Creates (and starts) a new stopwatch, using the specified time
113    * source.
114    *
115    * @since 15.0
116    */
117   public static Stopwatch createStarted(Ticker ticker) {
118     return new Stopwatch(ticker).start();
119   }
120 
121   /**
122    * Creates (but does not start) a new stopwatch using {@link System#nanoTime}
123    * as its time source.
124    *
125    * @deprecated Use {@link Stopwatch#createUnstarted()} instead.
126    */
127   @Deprecated
128   Stopwatch() {
129     this(Ticker.systemTicker());
130   }
131 
132   /**
133    * Creates (but does not start) a new stopwatch, using the specified time
134    * source.
135    *
136    * @deprecated Use {@link Stopwatch#createUnstarted(Ticker)} instead.
137    */
138   @Deprecated
139   Stopwatch(Ticker ticker) {
140     this.ticker = checkNotNull(ticker, "ticker");
141   }
142 
143   /**
144    * Returns {@code true} if {@link #start()} has been called on this stopwatch,
145    * and {@link #stop()} has not been called since the last call to {@code
146    * start()}.
147    */
148   public boolean isRunning() {
149     return isRunning;
150   }
151 
152   /**
153    * Starts the stopwatch.
154    *
155    * @return this {@code Stopwatch} instance
156    * @throws IllegalStateException if the stopwatch is already running.
157    */
158   public Stopwatch start() {
159     checkState(!isRunning, "This stopwatch is already running.");
160     isRunning = true;
161     startTick = ticker.read();
162     return this;
163   }
164 
165   /**
166    * Stops the stopwatch. Future reads will return the fixed duration that had
167    * elapsed up to this point.
168    *
169    * @return this {@code Stopwatch} instance
170    * @throws IllegalStateException if the stopwatch is already stopped.
171    */
172   public Stopwatch stop() {
173     long tick = ticker.read();
174     checkState(isRunning, "This stopwatch is already stopped.");
175     isRunning = false;
176     elapsedNanos += tick - startTick;
177     return this;
178   }
179 
180   /**
181    * Sets the elapsed time for this stopwatch to zero,
182    * and places it in a stopped state.
183    *
184    * @return this {@code Stopwatch} instance
185    */
186   public Stopwatch reset() {
187     elapsedNanos = 0;
188     isRunning = false;
189     return this;
190   }
191 
192   private long elapsedNanos() {
193     return isRunning ? ticker.read() - startTick + elapsedNanos : elapsedNanos;
194   }
195 
196   /**
197    * Returns the current elapsed time shown on this stopwatch, expressed
198    * in the desired time unit, with any fraction rounded down.
199    *
200    * <p>Note that the overhead of measurement can be more than a microsecond, so
201    * it is generally not useful to specify {@link TimeUnit#NANOSECONDS}
202    * precision here.
203    *
204    * @since 14.0 (since 10.0 as {@code elapsedTime()})
205    */
206   public long elapsed(TimeUnit desiredUnit) {
207     return desiredUnit.convert(elapsedNanos(), NANOSECONDS);
208   }
209 
210   private static TimeUnit chooseUnit(long nanos) {
211     if (DAYS.convert(nanos, NANOSECONDS) > 0) {
212       return DAYS;
213     }
214     if (HOURS.convert(nanos, NANOSECONDS) > 0) {
215       return HOURS;
216     }
217     if (MINUTES.convert(nanos, NANOSECONDS) > 0) {
218       return MINUTES;
219     }
220     if (SECONDS.convert(nanos, NANOSECONDS) > 0) {
221       return SECONDS;
222     }
223     if (MILLISECONDS.convert(nanos, NANOSECONDS) > 0) {
224       return MILLISECONDS;
225     }
226     if (MICROSECONDS.convert(nanos, NANOSECONDS) > 0) {
227       return MICROSECONDS;
228     }
229     return NANOSECONDS;
230   }
231 
232   private static String abbreviate(TimeUnit unit) {
233     switch (unit) {
234       case NANOSECONDS:
235         return "ns";
236       case MICROSECONDS:
237         return "\u03bcs"; // μs
238       case MILLISECONDS:
239         return "ms";
240       case SECONDS:
241         return "s";
242       case MINUTES:
243         return "min";
244       case HOURS:
245         return "h";
246       case DAYS:
247         return "d";
248       default:
249         throw new AssertionError();
250     }
251   }
252 }